/*
Copyright (C) Ariff Ehsan, Abdul Hadi Fikri

This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
*/

/*  Author : Ariff Ehsan
 *  Date   : June 18, 2009
 */


#include <system.h>

uint32_t timer_clock;

timer_t *timer_p_schedule;
timer_t *timer_p_run_start;
timer_t *timer_p_run_end;

void TIMER_insertSchedule(timer_t *t);
void TIMER_insertRun(timer_t *t);

void TIMER_removeSchedule(timer_t *t);
void TIMER_removeRun(timer_t *t);

void TIMER_expired(timer_t *t);

void TIMER_isr(void);

void TIMER_defaultFired(void);


void TIMER_init(void) {
#ifdef CLOCK_PULSE
	SET_BIT(CLOCK_PULSE_DIR, CLOCK_PULSE_PIN);
	CLR_BIT(CLOCK_PULSE_PORT, CLOCK_PULSE_PIN);
#endif

	DRIVER_CLOCK_INIT();

	timer_p_schedule = NULL;
	
	timer_p_run_start = NULL;
	timer_p_run_end = NULL;
	
	timer_clock = 0;

	DRIVER_CLOCK_REG(TIMER_isr);
}

void TIMER_isr(void) {
	timer_clock++;
	
#ifdef CLOCK_PULSE
	FLIP_BIT(CLOCK_PULSE_PORT, CLOCK_PULSE_PIN);
#endif
	
	timer_t *t = timer_p_schedule;
	timer_t *next;
	
	while (t != NULL) {
		next = t->next;
		
		if (t->expired == timer_clock) {
			TIMER_removeSchedule(t);
			TIMER_insertRun(t);
		}
		
		t = next;
	}
}

void TIMER_run(void) {
	ENTER_CRITICAL();
	
	timer_t *t = timer_p_run_start;
	
	if (t != NULL) {
		timer_p_run_start = t->next;
		
		if (t->periodic) {
			t->expired = timer_clock + t->period;
			TIMER_insertSchedule(t);
		}
		else {
			t->started = FALSE;
		}
		
		if (timer_p_run_start == NULL)
			timer_p_run_end = NULL;
		
		EXIT_CRITICAL();
		
		void (*run)(void) = t->run;
		(*run)();	
	}
	else {
		EXIT_CRITICAL();
	}
}

void TIMER_reg(timer_t *t, void (*run)(void)) {
	ENTER_CRITICAL();
	
	t->started = FALSE;
	t->periodic = FALSE;
	
	if (run == NULL)
		t->run = TIMER_defaultFired;
	else
		t->run = run;
	
	t->next = t->prev = NULL;
	
	EXIT_CRITICAL();
}

void TIMER_start(timer_t *t, uint16_t time) {
	ENTER_CRITICAL();
	
	if (t->started) {
		TIMER_removeSchedule(t);
		TIMER_removeRun(t);
	}
	
	t->started = TRUE;
	t->periodic = FALSE;
	
	t->expired = timer_clock + time;
	
	TIMER_insertSchedule(t);
	
	EXIT_CRITICAL();
}

void TIMER_startPeriodic(timer_t *t, uint16_t time) {
	ENTER_CRITICAL();
	
	if (t->started) {
		TIMER_removeSchedule(t);
		TIMER_removeRun(t);
	}
	
	t->started = TRUE;
	t->periodic = TRUE;
	
	t->period = time;
	t->expired = timer_clock + time;
	
	TIMER_insertSchedule(t);
	
	EXIT_CRITICAL();
}

void TIMER_stop(timer_t *t) {
	ENTER_CRITICAL();
	
	if (t->started) {
		TIMER_removeSchedule(t);
		TIMER_removeRun(t);
	}
	
	t->started = FALSE;
	t->periodic = FALSE;
	
	EXIT_CRITICAL();
}

uint32_t TIMER_getNow(void) {
	uint32_t t;
	
	ENTER_CRITICAL();
	
	t = timer_clock;
	
	EXIT_CRITICAL();
	
	return t;
}

BOOL TIMER_isRunning(timer_t *t) {
	BOOL status = t->started;
	return status;
}

void TIMER_expired(timer_t *t) {
	ENTER_CRITICAL();
	
	if (t->periodic) {
		t->expired = timer_clock + t->period;
		TIMER_insertSchedule(t);
	}
	else {
		t->started = FALSE;
	}
	
	EXIT_CRITICAL();
	
	void (*run)(void) = t->run;
	(*run)();
}


/**
 *  insert timer to schedule list
 */
void TIMER_insertSchedule(timer_t *t) {
	if (timer_p_schedule == NULL) {
		timer_p_schedule = t;
		t->prev = t->next = NULL;
	}
	else {
		t->next = timer_p_schedule;
		t->prev = NULL;
		
		timer_p_schedule->prev = t;
		timer_p_schedule = t;
	}
}

/**
 *  insert timer to run list
 */
void TIMER_insertRun(timer_t *t) {
	if (timer_p_run_start == NULL) {
		timer_p_run_start = timer_p_run_end = t;
		t->prev = t->next = NULL;
	}
	else {
		timer_p_run_end->next = t;
		
		t->prev = timer_p_run_end;
		t->next = NULL;
		
		timer_p_run_end = t;
	}
}


/**
 *  remove timer from schedule list
 */
void TIMER_removeSchedule(timer_t *t) {
	if (timer_p_schedule == t) {
		timer_p_schedule = t->next;
	}
	
	if (t->next != NULL)
		t->next->prev = t->prev;
	
	if (t->prev != NULL)
		t->prev->next = t->next;
	
	t->prev = t->next = NULL;
}


/**
 *  remove timer from run list
 */
void TIMER_removeRun(timer_t *t) {
	if (timer_p_run_start == t) {
		timer_p_run_start = t->next;
	}
	
	if (timer_p_run_start == NULL)
		timer_p_run_end = NULL;
	
	if (t->next != NULL)
		t->next->prev = t->prev;
	
	if (t->prev != NULL)
		t->prev->next = t->next;
	
	t->prev = t->next = NULL;
}


void TIMER_defaultFired(void) {
}

